/*
 * boot.S
 * After being loaded at address 0x7c00, it loads the kernel to address 0x10000.
 * Then disables all the interrupts and enter the protected mode. And jumps to
 * the kernel.
 */

BOOTSEG = 0x07c0
SYSSEG	= 0x1000
SYSSIZE = 0x8000	# Max value may be 0x9000. 
SYSEND	= SYSSEG + SYSSIZE

# For 1.44M.
sectors	= 18

.code16
.text
.global _start
_start:
	movw	$BOOTSEG, %ax
	movw	%ax, %ds
	movw	%ax, %es

	cli
	movw	%ax, %ss
	movw	$0x8000, %sp	# 0x8000 + 0x7c00 < 0x10000
	sti

	movb	$3, %ah
	xorb	%bh, %bh
	int	$0x10
	
	movw	$0x1301, %ax
	movw	$0x0007, %bx
	movw	$message,%bp
	movw	$9, %cx
	int	$0x10
	movb	$3, %ah
	xorb	%bh, %bh
	int	$0x10		# save cursor position in dx
	
	# read the second sector at 0x7c0:0x200
	call	read_boot
# get extent memory size
	call	get_e820

#	movb	$0x88,%ah
#	int	$0x15
#	movw	%ax,(506)

	
	movw	$SYSSEG, %ax
	movw	%ax, %es
	
	call	read_kernel
	call	kill_motor
	
# Save the current cursor at address 0x7c00 + 510, 0x7c00 + 511.
	movb	$3, %ah
	xorb	%bh, %bh
	int	$0x10
	movw	%dx, (510)

# Actually enter protected mode.
	movw	$SYSSEG,%ax
	movw	%ax,%es
	movw	%ax,%ds
	ljmp $0x1000,$0x200
	#.byte 0xea
	#.word 0x0
	#.word 0x1000

kb_wait:
	.word	0x00eb, 0x00eb	# Dealy
	inb	$0x64, %al	
	testb	$0x02, %al	# Keyboard buffer is full?
	jnz	kb_wait		# Yes, wait.
delay:
	ret

secnum:	.byte sectors - 2

read_boot:
	pushw   %dx
	xorw	%ax, %ax
	xorw	%dx, %dx
	int	$0x13
	
	movw 	$BOOTSEG, %bx
	movw	%bx, %es
	movw	$0x200, %bx
	movw	$0x0201, %ax
	movw	$2, %cx
	xorw	%dx, %dx
	call	read_track
	popw	%dx
	cmpw	$0xAA55, (1022)
	jnz	read_error
	ret	
read_error:
	movw	$0x1301, %ax
	movw	$0x0007, %bx
	movw	$error_msg,%bp
	movw	$7, %cx
	int	$0x10
die:
	jmp	die

read_kernel:
	xorw	%ax, %ax
	xorw	%dx, %dx
	int	$0x13
	
	xorw	%bx, %bx
	movb	$0x02, %ah	
	movb	secnum, %al	# %al == number of sectors
	movw	$0x0003, %cx	# %ch == track number, %cl == sector number
	movw	$0x0000, %dx	# %dh == head number, %dl == drive number
	
repeat_read:
	call	read_track
	
	xorb	%ah, %ah	# al == number of sectors which have been read
	shlw	$9, %ax
	addw	%ax, %bx	# %bx = %bx + (just read)
	movw	%bx, %ax	# %ax = %bx

	orw	%ax, %ax	# Is %ax == 0, if it's true, we have read 64K
	je	inc_es		# just have read 64K, only %ax == 0, set CF
	
	notw	%ax		# get the remaining size in one segment (64K).
	incw	%ax		# the same as 64K - %ax.

	shrw	$9, %ax		# unit from byte to 512 bytes.

	cmpw	$sectors, %ax	# could the remaining size hold one track ?
	jbe	set_sector	
	movw	$sectors, %ax
	jmp	set_sector

inc_es:
	call	print_dot
	movw	%es, %ax
	addw	$0x1000, %ax
	cmpw	$SYSEND, %ax
	jae	end_read
	movw	%ax, %es
	movw	$sectors, %ax

# %al contain the max secnum next time.
# %es : %bx, points the address next time. %cx, %dx, not being changed after 
# read_track.
set_sector:
	addb	secnum, %cl	# get the next sector to read
	decb	%cl		# get the number of sectors being read
				# within this track.
	movb	$sectors, secnum
	subb	%cl, secnum 	# get remaining sectors in track.
	jne	restore_cl	# if remaining some, then not change head

# when reamining none, the set sector to 1, and change head
set_head_track:
	movb	$sectors, secnum # change head, so set secnum to max
	movb	$1, %cl		# set sector
	addb	%dh, %ch	# if head == 1, increment track
	xorb	$1, %dh		# 0-->1, 1-->0
	jmp	set_secnum
	
restore_cl:
	incb	%cl
	
set_secnum:	
	cmpb	secnum, %al	# get the less
	jb	save_al
	movb	secnum, %al
	jmp	next
save_al:
	movb	%al, secnum

next:	
	movb	$2, %ah
	jmp	repeat_read

end_read:
	ret

print_dot:
	pushw	%ax
	
	movw	$0x0e2e, %ax
	int	$0x10

	popw	%ax
	ret

read_track:
	pushw	%ax
	pushw	%bx
	pushw	%cx
	pushw	%dx
	
	int	$0x13	# all params have been prepared for.

	jc	reset_fd
	
	popw	%dx
	popw	%cx
	popw	%bx
	popw	%ax
	ret

reset_fd:
	xorw	%ax, %ax
	xorw	%dx, %dx
	int	$0x13
	
	popw	%dx
	popw	%cx
	popw	%bx
	popw	%ax
	jmp	read_track

kill_motor:
	movw	$0x3f2, %dx
	xorb	%al, %al
	outb	%al, %dx
	ret

gdt:
	.word	0, 0, 0, 0

	.word	0x07ff		# limit == 8M (2048 * 4096)
	.word	0x0000		# base address = 0
	.word	0x9a00		# code read/exec
	.word	0x00c0		# unit = 4096, 386

	.word	0x07ff
	.word	0x0000
	.word	0x9200		# data read/write
	.word	0x00c0
	
idt_pdesc:
	.word	0
	.word	0, 0
	
gdt_pdesc:
	.word	8 * 3 - 1	# gdt limit = 24, 3 GDT entries
	.word	gdt + 0x7c00, 0	

message:
	.byte 0x0d, 0x0a
	.ascii "Loading"
error_msg:
	.byte 0xd, 0xa
	.ascii "Error"

E820Buf:
	.long 0, 0, 0, 0, 0	# INT 15:E820 data buffer
E820Max:
	.long 0			# Is E820 memory capped?
HighMemSize:
	.long 0			# End of memory pointer (bytes)
# to save memory size	
.org 506
E820Mem:
	.word 0,0
.org 510
	.word 0xaa55

get_e820:
		pushfl
		pushal

		movl $-(1 << 20), E820Max # Max amount of high memory
		movl $(1 << 20), E820Mem  # End of detected high memory
.start_over:
		movw $E820Buf, %di
		xor %ax, %ax
		movw $10, %cx
		rep stosw			# Clear buffer
		xor %ebx, %ebx			# Start with first record
		jmp .do_e820		# Skip "at end" check first time!
.int_loop:	and %ebx, %ebx			# If we're back at beginning...
		jz .e820_done			# ... we're done
.do_e820:	movl $0x0000E820, %eax
		movl $0x534D4150, %edx		# "SMAP" backwards
		xor %ecx, %ecx
		movb $20, %cl			# ECX <- 20 (size of buffer)
		movw $E820Buf, %di
		int $0x15
		jnc .no_carry
		# If carry, ebx == 0 means error, ebx != 0 means we're done
		and %ebx, %ebx
		jnz .e820_done
		jmp no_e820
.no_carry:
		cmpl $0x534D4150, %eax
		jne no_e820
		cmpw $20, %cx
		jb no_e820

#
# Look for a memory block starting at <= 1 MB and continuing upward
#
		cmpl $0, E820Buf + 4
		ja .int_loop			# Start >= 4 GB?
		movl E820Buf, %eax
		cmpl $1, E820Buf + 16
		je .is_ram			# Is it memory?
		#
		# Non-memory range.  Remember this as a limit; some BIOSes get the length
		# of primary RAM incorrect!
		#
.not_ram:
		cmpl $(1<<20), %eax
		jb .int_loop			# Starts in lowmem region
		cmpl E820Max, %eax
		jae .int_loop			# Already above limit
		movl %eax, E820Max		# Set limit
		jmp .int_loop

.is_ram:
		cmpl E820Mem, %eax
		ja .int_loop			# Not contiguous with our starting point
		addl E820Buf + 8, %eax
		jc .overflow
		cmpl $0, E820Buf + 12
		je .nooverflow
.overflow:
		orl $-1, %eax
.nooverflow:
		cmpl E820Mem, %eax
		jbe .int_loop			# All is below our baseline
		movl %eax, E820Mem
		jmp .start_over			# Start over in case we find an adjacent range

.e820_done:
		movl E820Mem, %eax
		cmpl E820Max, %eax
		jna .not_limited
		movl E820Max, %eax
.not_limited:
		cmpl $(1 << 20), %eax
		ja got_highmem			# Did we actually find memory?
		# otherwise fall through

#
# INT 15:E820 failed.  Try INT 15:E801.
#
no_e820:
		movw $0xe801, %ax			# Query high memory (semi-recent)
		int $0x15
		jc no_e801
		cmpw $0x3c00, %ax
		ja no_e801			# > 3C00h something's wrong with this call
		jb e801_hole			# If memory hole we can only use low part

		movw %bx, %ax
		shll $16, %eax			# 64K chunks
		addl $(16 << 20), %eax		# Add first 16M
		jmp got_highmem

#
# INT 15:E801 failed.  Try INT 15:88.
#
no_e801:
		movb $0x88, %ah			# Query high memory (oldest)
		int $0x15
		cmpw $14*1024, %ax			# Don't trust memory >15M
		jna e801_hole
		movw  $14*1024, %ax
e801_hole:
		andl $0xffff, %eax
		shll $10, %eax # Convert from kilobytes
		addl $(1 << 20), %eax	# First megabyte
got_highmem:
		movl %eax, E820Mem
		popal
		popfl
		ret				# Done!

.org 1022
	.word 0xAA55
	
